<?php
/**
 * 
 * vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
 *
 * A class for converting back and forth between a PHP array and a PostgreSQL 
 * array value. This only works on a single dimension array.
 *
 * @category Xdms
 * 
 * @package Xdms_Sql_Array PostgreSQL array two-way converter.
 * 
 * @author Abu Bakar Al-Idrus <alidrus@gmail.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id$
 * 
 */
class Xdms_Sql_Array extends Solar_Base
{
    /**
     *
     * Array to hold array field data.
     *
     * @var array
     *
     */
    protected $_data=array();

    /**
     *
     * String to hold the SQL formatted array.
     *
     * @var string
     *
     */
    protected $_string_data='';

    /**
     *
     * Current position within the SQL formatted array string.
     *
     * @var int
     *
     */
    protected $_current_position=0;

    /**
     *
     * Current index of the data array.
     *
     * @var int
     *
     */
    protected $_index=0;


    /*
     *
     * Get (import) the value of an array field in SQL into the data array.
     *
     * @param string $data Value of the array field in string form.
     *
     * @param string $separator Value of the array field separator used.
     *
     */
    public function get($data, $separator = ',') {
        // set the string data
        $this->_string_data = $data;

        // check if string has index
        $have_index = preg_match("/^(\[([0-9]+):([0-9]+)\])=/", $this->_string_data, $matches);

        // record the index start
        $this->_index = $have_index ? (int) $matches[2] : 0;

        // strip away the index from the string data (if any)
        $this->_string_data = $have_index
            ? preg_replace("/^\[[0-9]+:[0-9]+\]=/", "", $this->_string_data)
            : $this->_string_data;

        // strip away the enclosing parenthesis at beginning and end
        $this->_string_data=preg_replace('/^\{/', '', $this->_string_data);
        $this->_string_data=preg_replace('/\}$/', '', $this->_string_data);

        // reset character position tracker
        $this->_current_position = 0;

        // reset escape sequence flags
        $this->_eseq_quote = false;
        $this->_eseq_backslash = false;

        // loop while we are within string boundary
        while ($char = $this->_nextChar()) {
            switch (true) {
                case $char === '"' && !$this->_eseq_quote && !$this->_eseq_backslash:
                    // set escape sequence quote
                    $this->_eseq_quote=true;
                    break;
                case $char === '"' && $this->_eseq_quote && !$this->_eseq_backslash:
                    // unset escape sequence quote
                    $this->_eseq_quote=false;
                    break;
                case $char === $separator && !$this->_eseq_quote:
                    $this->_index++;
                    break;
                case $char === '\\' && !$this->_eseq_backslash:
                    // set escape sequence backslash
                    $this->_eseq_backslash = true;
                    break;
                case $char === '\\' && $this->_eseq_backslash:
                    // unset escape sequence backslash
                    $this->_eseq_backslash = false;
                    // grab the current character as is
                    $this->_append($char);
                    break;
                default:
                    // unset escape sequence backslash
                    $this->_eseq_backslash = false;
                    // append the current character as is
                    $this->_append($char);
                    break;
            }
        }
    }

    /*
     *
     * Set the data array. Calling this multiple times will append the value to the class's 
     * data array.
     *
     * @param string|array $data A string or array containing the data to 
     * append to the data array.
     *
     * @return void
     *
     */
    public function set($data = null) {
        // give it up if data is empty
        if (!$data) {
            return;
        }

        // if data is not an array, cast to array
        if (!is_array($data)) {
            $data = (array) $data;
        }

        // go through each array element
        foreach($data as $value) {
            // add slashes if value contains slashes
            if (preg_match('/\\\\/', $value)) {
                $value = str_replace('\\', '\\\\', $value);
            }

            // add slashes if value contains double quotes
            if (preg_match('/"/', $value)) {
                $value = str_replace('"', '\\"', $value);
            }

            // enclose value in double quotes if it contains spaces
            if (preg_match('/ /', $value)) {
                $value = '"'.$value.'"';
            }

            // append to data array
            $this->_data[]=$value;
        }
    }

    /*
     *
     * Fetch the current data.
     *
     * @param bool $as_array If true, fetch the data as an array, else return 
     * an SQL formatted array string.
     *
     */
    public function fetch($as_array = false) {
        // return as an array?
        if ($as_array) {
            return $this->_data;
        }

        // check if there is any data
        if (! $this->_data) {
            // return empty sql array
            return '{}';
        }

        // return the sql array
        $index = '[0:'.(sizeof($this->_data) - 1).']';
        return $index.'={'.implode(',', $this->_data).'}';
    }

    /*
     *
     * Reset the data array.
     *
     * @return void
     *
     */
    public function reset() {
        $this->_data=array();
    }

    /*
     *
     * Advance current position, return the character that we found before 
     * advancing if we have not already transgressed the boundary of the 
     * string. Returns false if we have overstepped the boundary of the string.
     *
     * @return bool|string
     *
     */
    protected function _nextChar() {
        // have we transgressed the string boundary?
        if ($this->_current_position >= strlen($this->_string_data)) {
            return false;
        }

        // get current character
        $char = substr($this->_string_data, $this->_current_position, 1);

        // increment string position tracker
        $this->_current_position++;

        // return character
        return $char;
    }

    /**
     *
     * Append the character to the current data array element
     *
     * @return void
     *
     */
    protected function _append($char) {
        $this->_data[$this->_index] = array_key_exists($this->_index, $this->_data)
            ? $this->_data[$this->_index].$char
            : $char;
    }
}
